#!/usr/bin/env python2.5
# This file is part of Loquacious Etymologist.
# Loquacious Etymologist is copyright (c) Nicholas Pilon 2008.
# The algorithm used by Loquacious Etymologist was designed by John Morrow.
# 
# Loquacious Etymologist is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# Loquacious Etymologist is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with Loquacious Etymologist.  If not, see <http://www.gnu.org/licenses/>.

# TODO: Number of syllables selection. Random/fixed checkboxes, spinner for when fixed.
# TODO: Reorganize controls?
# TODO: Option to append to list instead of replacing?
# TODO: Add list of clusters, allowing editing of it:
# - Inspector pane for editing clusters. (Incl. successor lists, frequencies)
# - Copying and pasting of clusters to move between languages.
# - Adding new clusters.
# - Deleting clusters.
# - Put it behind a disclosure triangle. (wxSplitterWindow?)

import wx
import wx.html
import wordlist
import os
import copy
import codecs

ID_IMPORT_WORDS = wx.ID_HIGHEST + 1
ID_MY_COPY = wx.ID_HIGHEST + 2
ID_LICENSE = wx.ID_HIGHEST + 3

OpenWindows = []

class LanguageWindowController(object):
	def __init__(self, language = None):
		self.language = language or wordlist.Language()
		self.wordList = []
		self.filepath = ''
		self.filename = 'untitled.lel'
		self.changed = False
		self.hasRealFile = False
	
	def languageBlank(self):
		"""Is the language for this window 'empty'?"""
		return len(self.language.clusterForCharacters) == 0
	
	def setVowels(self):
		"""Handles setting the language's vowels."""
		vowelSet = set()
		for vowel in self.window.vowels.GetValue():
			vowelSet.add(vowel)
		vowelList = list(vowelSet)
		vowelList.sort()
		vowels = u''.join(vowelList)
		self.language.vowels = vowels
		self.window.vowels.SetValue(vowels)
	
	def loadLanguage(self, filepath, filename):
		"""Loads a language into this controller."""
		print self.window
		wx.BeginBusyCursor()
		openPath = os.path.join( filepath, filename )
		self.language = wordlist.Language()
		self.language.load(openPath)
		self.filename = filename
		self.filepath = filepath
		self.window.vowels.SetValue(self.language.vowels)
		self.window.ignoreDiacriticals.SetValue(self.language.ignoreDiacriticals)
		self.window.SetTitle(self.filename)
		self.window.GetStatusBar().SetStatusText('Language Loaded from %s' % openPath)
		self.changed = False
		self.hasRealFile = True
		wx.EndBusyCursor()
		print self.language.vowelClusterCountFrequency
	
	def saveLanguage(self, filepath, filename):
		"""Save the language from this controller to a file."""
		wx.BeginBusyCursor()
		if self.window.vowels.GetValue() != self.language.vowels:
			self.setVowels()
		if self.window.ignoreDiacriticals.GetValue() != self.language.ignoreDiacriticals:
			self.language.ignoreDiacriticals = self.window.ignoreDiacriticals.GetValue()
		savePath = os.path.join( filepath, filename )
		self.language.dump(savePath)
		self.window.GetStatusBar().SetStatusText('Language Saved to %s' % savePath)
		self.filepath = filepath
		self.filename = filename
		self.window.SetTitle(self.filename)
		self.changed = False
		self.hasRealFile = True
		wx.EndBusyCursor()
	
	def importWords(self, filepath, filename):
		"""Import words from a word list file into the language."""
		wx.BeginBusyCursor()
		if self.window.vowels.GetValue() != self.language.vowels:
			self.setVowels()
		if self.window.ignoreDiacriticals.GetValue() != self.language.ignoreDiacriticals:
			self.language.ignoreDiacriticals = self.window.ignoreDiacriticals.GetValue()
		openPath = os.path.join( filepath, filename )
		wordlist_file = codecs.open(openPath, 'r', 'utf8')
		self.language = wordlist.Language()
		for line in wordlist_file:
			for word in line.strip().split():
				self.language.integrateWord(word)
		wordlist_file.close()
		self.window.GetStatusBar().SetStatusText('Word List Imported.')
		self.changed = True
		wx.EndBusyCursor()
	
	def generateWords(self, numberOfWords):
		"""Generate words from this language."""
		wx.BeginBusyCursor()
		if self.window.vowels.GetValue() != self.language.vowels:
			self.setVowels()
		if self.window.ignoreDiacriticals.GetValue() != self.language.ignoreDiacriticals:
			self.language.ignoreDiacriticals = self.window.ignoreDiacriticals.GetValue()
		wordSet = set()
		while len(wordSet) < numberOfWords:
			word = self.language.generateWord()
			wordSet.add(word)
		self.wordList = list(wordSet)
		self.wordList.sort()
		wx.EndBusyCursor()
	

class LanguageWindow(wx.Frame):
	def __init__(self, controller = None, *args, **kws):
		# Save our controller.
		self.controller = controller or LanguageWindowController()
		
		# Create the frame itself.
		wx.Frame.__init__(self, *args, **kws)
		
		# Create the widgets.
		self.CreateStatusBar()
		self.wordLabel = wx.StaticText(parent = self, label = 'Words:', style = wx.ALIGN_LEFT)
		self.wordDisplay = wx.ListBox(parent = self, style = wx.LB_EXTENDED | wx.LB_NEEDED_SB)
		self.numberOfWords = wx.SpinCtrl(parent = self, min = 0, initial = 50, value = '50')
		self.numberOfWordsLabel = wx.StaticText(parent = self, label = 'Number of Words:', style = wx.ALIGN_RIGHT)
		self.generateButton = wx.Button(parent = self, label = 'Generate Words')
		self.vowels = wx.TextCtrl(parent = self, value = u'aeiou', style = wx.TE_PROCESS_ENTER)
		self.vowelsLabel = wx.StaticText(parent = self, label = 'Vowels:', style = wx.ALIGN_RIGHT)
		self.ignoreDiacriticals = wx.CheckBox(parent = self, label = 'Ignore Diacritical Marks')
		
		# Create the menus and menu bar.
		self.filemenu = wx.Menu()
		self.newItem = self.filemenu.Append(wx.ID_NEW)
		self.openItem = self.filemenu.Append(wx.ID_OPEN)
		self.filemenu.AppendSeparator()
		self.closeItem = self.filemenu.Append(wx.ID_CLOSE, "&Close\tCtrl+w")
		self.saveItem = self.filemenu.Append(wx.ID_SAVE)
		self.saveAsItem = self.filemenu.Append(wx.ID_SAVEAS, "Save &As...\tCtrl+Shift+s")
		self.importItem = self.filemenu.Append(ID_IMPORT_WORDS, "&Import Words...\tCtrl+i")
		self.aboutItem = self.filemenu.Append(wx.ID_ABOUT)
		self.exitItem = self.filemenu.Append(wx.ID_EXIT)
		self.editmenu = wx.Menu()
		self.editmenu.Append(wx.ID_CUT)
		self.editmenu.Append(wx.ID_COPY)
		self.copyWordsItem = self.editmenu.Append(ID_MY_COPY, "&Copy Words\tCtrl+c")
		self.editmenu.Append(wx.ID_PASTE)
		self.selectAllItem = self.editmenu.Append(wx.ID_SELECTALL, "Select All\tCtrl+a")
		self.editmenu.Enable(ID_MY_COPY, False)
		self.helpmenu = wx.Menu()
		self.helpItem = self.helpmenu.Append(wx.ID_HELP, "Loquacious Etymologist Help")
		self.licenseItem = self.helpmenu.Append(ID_LICENSE, "License")
		self.menuBar = wx.MenuBar()
		self.menuBar.Append(self.filemenu, "&File")
		self.menuBar.Append(self.editmenu, "&Edit")
		self.menuBar.Append(self.helpmenu, "&Help")
		self.SetMenuBar(self.menuBar)
		
		# Bind events
		self.generateButton.Bind(wx.EVT_BUTTON, self.onGenerate)
		self.wordDisplay.Bind(wx.EVT_LISTBOX, self.onSelectWord)
		self.wordDisplay.Bind(wx.EVT_SET_FOCUS, self.onSelectWord)
		self.wordDisplay.Bind(wx.EVT_KILL_FOCUS, self.onWordsLoseFocus)
		self.Bind(wx.EVT_MENU, self.onNew, self.newItem)
		self.Bind(wx.EVT_MENU, self.onOpen, self.openItem)
		self.Bind(wx.EVT_MENU, self.onClose, self.closeItem)
		self.Bind(wx.EVT_CLOSE, self.onClose)
		self.Bind(wx.EVT_MENU, self.onSave, self.saveItem)
		self.Bind(wx.EVT_MENU, self.onSaveAs, self.saveAsItem)
		self.Bind(wx.EVT_MENU, self.onImport, self.importItem)
		self.Bind(wx.EVT_MENU, self.onAbout, self.aboutItem)
		self.Bind(wx.EVT_MENU, self.onExit, self.exitItem)
		self.Bind(wx.EVT_MENU, self.onCopy, self.copyWordsItem)
		self.Bind(wx.EVT_MENU, self.onHelp, self.helpItem)
		self.Bind(wx.EVT_MENU, self.onLicense, self.licenseItem)
		
		# Add and position the widgets.
		self.sizer = wx.GridBagSizer()
		outerBorder = 5
		self.sizer.Add(self.wordLabel, pos = (0, 0), border = outerBorder, flag = wx.TOP | wx.LEFT)
		self.sizer.Add(self.wordDisplay, pos = (1, 0), span = (1, 4), border = outerBorder, flag = wx.GROW | wx.LEFT | wx.RIGHT)
		self.sizer.Add(self.vowelsLabel, pos = (2,1), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
		self.sizer.Add(self.vowels, pos = (2,2), span = (1, 2), border = outerBorder, flag = wx.ALIGN_CENTER | wx.ALL | wx.GROW)
		self.sizer.Add(self.ignoreDiacriticals, pos = (3, 2), span = (1, 2), border = outerBorder, flag = wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.RIGHT | wx.GROW)
		self.sizer.Add(self.numberOfWordsLabel, pos = (4, 1), border = outerBorder, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.BOTTOM)
		self.sizer.Add(self.numberOfWords, pos = (4, 2), border = outerBorder, flag = wx.ALIGN_CENTER | wx.BOTTOM | wx.GROW)
		self.sizer.Add(self.generateButton, pos = (4, 3), border = outerBorder, flag = wx.ALIGN_CENTER | wx.BOTTOM | wx.RIGHT)
		self.sizer.AddGrowableRow(1)
		self.sizer.AddGrowableCol(0)
		self.SetSizer(self.sizer)
		self.sizer.SetSizeHints(self)
		self.SetAutoLayout(1)
		self.sizer.Fit(self)
		self.SetSize((50, 500))
		self.Show(1)
		self.SetDefaultItem(self.generateButton)
		
		# Add us to the list of open windows.
		global OpenWindows
		OpenWindows.append(self)
	
	def Destroy(self):
		wx.Frame.Destroy(self)
		global OpenWindows
		OpenWindows.remove(self)
	
	def onNew(self, event):
		"""Create a new language and a new window to store it."""
		newWindow = LanguageWindow(parent = None, id = -1, title = 'untitled.lel')
	
	def onOpen(self, event):
		"""Open a language file. This will create a new window if the current window 'has stuff in it', or use the existing window if it's 'blank'."""
		dlg = wx.FileDialog(parent = self, message = "Open Language", defaultDir = self.controller.filepath, wildcard = "*.lel", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
		if dlg.ShowModal() == wx.ID_OK:
			if self.controller.languageBlank():
				# Load into this window.
				self.controller.loadLanguage( filepath = dlg.GetDirectory(), filename = dlg.GetFilename() )
				self.onGenerate(event)
			else:
				# Load into a new window.
				newController = LanguageWindowController(serializer = self.controller.serializer)
				newWindow = LanguageWindow(controller = newController, parent = None, id = -1, title = dlg.GetFilename())
				newController.loadLanguage( filepath = dlg.GetDirectory(), filename = dlg.GetFilename() )
				newWindow.onGenerate(event)
	
	def onClose(self, event):
		"""Close the window, prompting to save the language if it's not saved."""
		if self.controller.changed:
			response = wx.MessageBox(message = "Save changes to %s before closing?" % (self.controller.filename), caption = "Confirm", style = wx.YES_NO | wx.CANCEL, parent = self)
			if response == wx.CANCEL:
				# Don't close.
				return
			if response == wx.YES:
				# Save and then close.
				self.onSave(event)
		self.Destroy()
	
	def onSave(self, event):
		"""Save the existing language to a file for later reference."""
		if self.controller.hasRealFile:
			self.controller.saveLanguage( self.controller.filepath, self.controller.filename )
		else:
			self.onSaveAs(event)
	
	def onSaveAs(self, event):
		"""Save As the existing language to a file for later reference."""
		dlg = wx.FileDialog(parent = self, message = "Save Language As...", defaultDir = self.controller.filepath, defaultFile = self.controller.filename, wildcard = "*.lel", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
		if dlg.ShowModal() == wx.ID_OK:
			self.controller.saveLanguage( dlg.GetDirectory(), dlg.GetFilename() )
	
	def onImport(self, event):
		"""Imports words from a file into this language."""
		dlg = wx.FileDialog(parent = self, message = "Import Word List File", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_CHANGE_DIR)
		if dlg.ShowModal() == wx.ID_OK:
			self.controller.importWords( dlg.GetDirectory(), dlg.GetFilename() )
	
	def onAbout(self, event):
		"""Display an About box showing information on the program."""
		info = wx.AboutDialogInfo()
		info.AddDeveloper('Nick Pilon')
		info.AddDeveloper('John Morrow')
		info.SetCopyright('Copyright (C) Nick Pilon 2007. Licensed under the GPL version 3.')
		info.SetDescription('A program to randomly generate words that could conceivably be part of a language.\nLanguages are built from lists of words actually found in that language.\n\nThis program is licensed under version 3 of the GNU General Public License.\nSee "License" under the Help menu for details.')
		info.SetVersion('0.5')
		info.SetWebSite('http://www.versionthis.com/~npilon')
		wx.AboutBox(info)
	
	def onExit(self, event):
		"""Exit the application. Close all windows, then exit."""
		global OpenWindows
		for window in copy.copy(OpenWindows):
			window.onClose(event)
	
	def onGenerate(self, event):
		"""Generate a list of words, sort them, and display them in the list box."""
		self.controller.generateWords(self.numberOfWords.GetValue())
		self.wordDisplay.Clear()
		self.wordDisplay.InsertItems(items = self.controller.wordList, pos = 0)
	
	def onSelectWord(self, event):
		"""Triggered when the listbox selection changes. Enable or disable the copy menu item appropriately."""
		self.editmenu.Enable(ID_MY_COPY, len(self.wordDisplay.GetSelections()) != 0)
	
	def onWordsLoseFocus(self, event):
		"""Triggered when the listbox loses focus. Disables the word copy command."""
		self.editmenu.Enable(ID_MY_COPY, False)
	
	def onCopy(self, event):
		"""Copy the selected words, separated by spaces, to the system clipboard."""
		copyWords = []
		for selectedItem in self.wordDisplay.GetSelections():
			copyWords.append(self.controller.wordList[selectedItem])
		copyWords.sort()
		copyPhrase = ' '.join(copyWords)
		if wx.TheClipboard.Open():
			wx.TheClipboard.SetData(wx.TextDataObject(copyPhrase))
			wx.TheClipboard.Close()
	
	def onHelp(self, event):
		"""Display the help browser."""
		HelpController.Display('usage.html')
	
	def onLicense(self, event):
		"""Show the program's license."""
		HelpController.Display('license.html')
	
	def controller():
		doc = "The controller property."
		def fget(self):
			return self._controller
		
		def fset(self, value):
			self._controller = value
			self._controller.window = self
		
		def fdel(self):
			del self._controller
			del self._controller.window
		
		return locals()
	
	controller = property(**controller())

if __name__ == '__main__':
	app = wx.PySimpleApp()
	app.SetAppName('Loquacious Etymologist')
	frame = LanguageWindow(parent = None, id = -1, title = 'untitled.lel')
	global HelpController
	HelpController = wx.html.HtmlHelpController()
	wx.FileSystem.AddHandler(wx.ZipFSHandler());
	HelpController.AddBook('help.htb')
	app.MainLoop()
